import asyncio
import json
import logging
import re
import threading
import time

import serial
from pynput import keyboard
from pynput.keyboard import Key
from serial import PARITY_EVEN

"""
    32位系统
"""


class SerialPort:
    def __init__(self, conf):
        # 是否开启端口监听
        self.openPort = conf['service']['open_serial_port'] == '1'
        # 是否开启键盘监听
        self.openKeyboard = conf['service']['open_keyboard'] == '1'
        self.portHeight = "COM2"  # 身高串口
        self.baudrateHeight = 115200  # 身高波特率

        self.portPressure = "COM3"  # 血压串口
        self.baudratePressure = 2400  # 身高波特率
        self.timeout = 0.5

        self.pressureSplit = conf['pressure']['split'] if conf.has_option(section='pressure',
                                                                          option='split') else ' '  # 分割符
        self.pressureSYS = int(conf['pressure']['sys'])  # 收缩压所在位置
        self.pressureDIA = int(conf['pressure']['dia'])  # 舒张压所在位置

        self.examNo = {'1': '', '2': '', '3': ''}  # 患者体检号,用户表示当前数据的归属
        self.heightSer = None
        self.pressureSer = None

        self.CONNECTIONS = set()  # 存放已连接的客户端信息,以便广播消息
        # 扫码枪扫描的体检号
        self.scanExam = ''
        self.keyboardLoop = None  # 新开一个loop,用于在同步代码中调用异步方法
        # 记录键盘输入时间
        self.pressTime = 0
        self.logger=logging.getLogger('main')

    async def broad_cast_msg(self, message, msg_type):
        """
        向客服端广播消息,消息发送之后将体检号置为空
        :param message: 消息体
        :param msg_type: 消息类型 身高为1, 血压为2,3为监听键盘输入结果
        :return:
        """
        self.logger.info('发送消息:{}'.format(message), msg_type)
        try:
            if len(self.examNo) > 0:
                no = ''
                if str(msg_type) in self.examNo.keys():
                    no = self.examNo[str(msg_type)]
                for c in self.CONNECTIONS:
                    await c.send(json.dumps({"examNo": no, "type": msg_type, "data": message}))
                self.examNo[str(msg_type)] = ''
            else:
                for c in self.CONNECTIONS:
                    await c.send(json.dumps({"examNo": "", "type": msg_type, "data": message}))
                self.examNo.clear()
        except Exception as e:
            self.logger.error('发送错误:{}'.format(e))
            return False
        return True

    # 打开身高串口
    async def openHeightSerial(self):
        if self.heightSer is None:
            try:
                self.heightSer = serial.Serial(port=self.portHeight,
                                               baudrate=self.baudrateHeight,
                                               timeout=self.timeout,
                                               bytesize=8)
            except Exception as e:
                self.logger.error('打开身高串口出错:{}'.format(e))
                return True
        if not self.heightSer.isOpen():  # 如果端口没有开启,则开启端口
            self.heightSer.open()
        self.heightSer.flushInput()
        self.heightSer.flushOutput()
        while True:
            data = self.heightSer.readall()  # 读取一个字节的数据
            try:
                if data:
                    decoded_data = data.decode("unicode-escape")
                    self.logger.info('身高解码数据:{}'.format(decoded_data))
                    subData = decoded_data.split('\n')
                    subData = [x for x in subData if 'cm' in x and 'kg' in x]
                    if subData:
                        strData = subData[0]
                        h = re.findall(r'\d+\.?\d*[cm]+', strData)[0].strip('cm')
                        w = re.findall(r'\d+\.?\d*[kg]+', strData)[0].strip('kg')
                        send = h + ',' + w
                        if send and self.CONNECTIONS:
                            await self.broad_cast_msg(send, 1)
            except Exception as e:
                self.logger.error('身高解码出错:{}'.format(e))

    # 打开血压串口
    async def openPressureSerial(self):
        if self.pressureSer is None:
            try:
                self.pressureSer = serial.Serial(port=self.portPressure,
                                                 baudrate=self.baudratePressure,
                                                 timeout=self.timeout,
                                                 bytesize=7,
                                                 parity=PARITY_EVEN)
            except Exception as e:
                self.logger.error('打开血压串口出错:{}'.format(e))
                return True

        if not self.pressureSer.isOpen():  # 如果端口没有开启,则开启端口
            r = self.pressureSer.open()
            self.logger.info('打开血压串口成功')
        self.pressureSer.flushInput()
        self.pressureSer.flushOutput()
        while True:
            data = self.pressureSer.readall()  # 读取一个字节的数据
            if data:
                try:
                    decoded_data = data.decode("GBK")
                    self.logger.info('血压串口解码数据:{}'.format(decoded_data))
                    if decoded_data and self.CONNECTIONS:
                        split_data = decoded_data.split(self.pressureSplit)
                        await self.broad_cast_msg(split_data[self.pressureSYS] + ',' + split_data[self.pressureDIA], 2)
                except Exception as e:
                    self.logger.error('血压解码出错:{}'.format(e))

    # 键盘按下事件
    def keyPress(self, key):
        """
        如果pressTime=0 将当前时间赋值给pressTime,并且将保存的数据清空
        如果当前时间跟上次时间相差过长,则清空保存数据,同时将pressTime赋值0,并且不再走下面的流程
        如果有回车键输入,则判断pressTime 与当前时间差,如果时间过长,则清空保存数据,同时pressTime置为0
        如果时间差在允许范围内,则说明是扫码结果,将结果广播出去,清空数据,同时pressTime置为0
        :param key:
        :return:
        """
        if self.pressTime == 0:
            self.pressTime = time.time()
            self.scanExam = ''
        elif time.time() - self.pressTime > 0.5:
            # 如果两次按键间隔时间过长,则清除存储的数据
            self.scanExam = ''
            self.pressTime = 0
            return
        try:
            if hasattr(key, 'char') and key.char.isdigit():
                self.scanExam += key.char
            else:
                self.pressTime = 0
            if key == Key.enter and self.scanExam and re.match(r'^[0-9]*$', self.scanExam):
                self.logger.info('键盘监听数据:{}'.format(self.scanExam))
                if self.scanExam:
                    coroutine = self.broad_cast_msg(self.scanExam, 3)
                    if self.keyboardLoop is None:
                        self.keyboardLoop = asyncio.new_event_loop()
                    asyncio.set_event_loop(self.keyboardLoop)
                    future = asyncio.gather(coroutine)
                    self.keyboardLoop.run_until_complete(future)
                    self.scanExam = ''
                    self.pressTime = 0
        except AttributeError as e:
            self.scanExam = ''
            self.pressTime = 0
            self.logger.error('键盘监听出错:{}', format(e))
        except TypeError as e:
            self.pressTime = 0
            self.logger.error('键盘监听出错:{}', format(e))

    # 监听键盘输入
    async def watchKeyboard(self):
        self.logger.info('watchKeyboard')
        with keyboard.Listener(on_press=self.keyPress) as lsn:
            lsn.join()

    def setExamNo(self, examNo, type_exam):
        if type_exam and examNo and len(str(type_exam)) > 0 and len(str(examNo)) > 0:
            self.examNo[str(type_exam)] = str(examNo)

    def thread_loop_height(self, loop):
        asyncio.set_event_loop(loop)
        future = asyncio.gather(self.openHeightSerial())
        loop.run_until_complete(future)

    def thread_loop_pressure(self, loop):
        asyncio.set_event_loop(loop)
        future = asyncio.gather(self.openPressureSerial())
        loop.run_until_complete(future)

    def thread_loop_keyboard(self, loop):
        asyncio.set_event_loop(self.loop)
        loop.run_until_complete(asyncio.gather(self.watchKeyboard()))

    def start(self):
        if self.openPort:
            # 开启一个新的线程监听身高串口信息
            thread_loop = asyncio.new_event_loop()
            t = threading.Thread(target=self.thread_loop_height, args=(thread_loop,))
            t.daemon = True
            t.start()

            # 开启一个新的线程监听血压串口信息
            pressure_loop = asyncio.new_event_loop()
            tp = threading.Thread(target=self.thread_loop_pressure, args=(pressure_loop,))
            tp.daemon = True
            tp.start()
        if self.openKeyboard:
            # 开启一个新线程监听键盘输入
            key_loop = asyncio.new_event_loop()
            tk = threading.Thread(target=self.thread_loop_keyboard, args=(key_loop,))
            tk.daemon = True
            tk.start()
